package ca.qc.bdeb.jeuxdesboites.model;

import ca.qc.bdeb.jeuxdesboites.commun.Coup;
import ca.qc.bdeb.jeuxdesboites.commun.EvenementPartie;
import ca.qc.bdeb.jeuxdesboites.commun.IDJoueurs;
import ca.qc.bdeb.mvc.Observable;
import ca.qc.bdeb.mvc.Observateur;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Eric Wenaas <eric.wenaas@bdeb.qc.ca>
 */
public final class GestionPartie implements Observable {
    
    public final int LARGEUR_TABLEAU;
    public final int HAUTEUR_TABLEAU;

    private IDJoueurs joueurActuel;
    private Horloge horloge;
    private TableauJeu tableau;
    private Joueur joueurs[]; 
    private List<Observateur> observateurs;
    private Thread miseAJourTemps;
    private boolean partieEnCours;
    
    public GestionPartie(int largeur, int hauteur) {
        LARGEUR_TABLEAU = largeur;
        HAUTEUR_TABLEAU = hauteur;
        joueurActuel = null;
        horloge = null;
        tableau = null;
        joueurs = new Joueur[2];
        observateurs  = new LinkedList<>();
        miseAJourTemps = null;
        partieEnCours = false;
    }

    /**
     * Mise en place de la partie. Nécessaire d'appeler cette méthode avant de
     * démarrer une nouvelle partie.
     * 
     * @param nomJoueurHumain Le pseudonyme du joueur humain
     * @param tempsEnMillis le temps des deux horloges
     * @param humainPremier vrai si l'humain doit jouer le premier coup
     */
    public void initialiserPartie(String nomJoueurHumain, long tempsEnMillis,
                                  boolean humainPremier) {
        horloge = new Horloge(tempsEnMillis);
        joueurs[IDJoueurs.JOUEUR_HUMAIN.ordinal()] = new JoueurHumain();
        joueurs[IDJoueurs.JOUEUR_HUMAIN.ordinal()].setNom(nomJoueurHumain);
        joueurs[IDJoueurs.JOUEUR_ARTIFICIEL.ordinal()] = new JoueurArtificiel(this);
        observateurs.add((JoueurArtificiel)joueurs[IDJoueurs.JOUEUR_ARTIFICIEL.ordinal()]);
        if (humainPremier) {
            joueurActuel = IDJoueurs.JOUEUR_HUMAIN;
        } else {
            joueurActuel = IDJoueurs.JOUEUR_ARTIFICIEL;
        }
        tableau = new TableauJeu(LARGEUR_TABLEAU, HAUTEUR_TABLEAU);
    }
    

    /**
     * Démarre l'horloge du premier joueur à jouer. Préalable à toute acceptation
     * de coups.
     */
    public void demarrerPartie() {
        horloge.demarrerHorloges();
        horloge.activer(joueurActuel);
        demarrerMiseAjourTemps();
        partieEnCours = true;
        System.out.println("Démarrage d'une partie " + "largeur: " + LARGEUR_TABLEAU +
                           " hauteur: " + HAUTEUR_TABLEAU);
    }
    
    /**
     * La partie doit préalablement avoir été démarrée par demarrerPartie.
     * 
     * @param joueur
     * @param unCoup 
     */
    public void jouerCoup(IDJoueurs joueur, Coup unCoup) {
        if (! partieEnCours) {
            aviserObservateurs(EvenementPartie.MESSAGE_ERREUR, "La partie n'est pas démarrée");
            return;
        }
        System.out.println(joueur.toString() + ": " + unCoup.toString());

        boolean continuer;  // est vrai si le joueur ferme une boîte

        continuer = tableau.jouerCoup(joueur, unCoup);
        aviserObservateurs(EvenementPartie.CHANGEMENT_TABLEAU, null);
        if (tableau.isComplet()) {
            terminerPartie();
        } else {
            if (! continuer) {
                changerJoueurActuel();
            }
            aviserObservateurs(EvenementPartie.COUPS_REQUIS, null);
        }
    }
    
    public int compterPoints(IDJoueurs joueur) {
        return tableau.getNombrePoints(joueur);
    }

    private void terminerPartie() {
        horloge.arreterHorloges();
        aviserObservateurs(EvenementPartie.CHANGEMENT_HORLOGE, this);
        aviserObservateurs(EvenementPartie.PARTIE_TERMINEE, null);
        partieEnCours = false;
    }    

    private void changerJoueurActuel() {
        int indiceJoueurActuel = (joueurActuel.ordinal() + 1) % IDJoueurs.values().length; 
        joueurActuel = IDJoueurs.values()[indiceJoueurActuel];
        horloge.activerProchaineHorloge();        
    }
    
    public TableauJeu getTableau() {
        return tableau;
    }
    
    Joueur getJoueur(IDJoueurs id) {
        return joueurs[id.ordinal()];
    }
    
    public IDJoueurs getJoueurActuel() {
        return joueurActuel;
    }
    
    public String getNomJoueur(IDJoueurs idJoueurs) {
        return joueurs[idJoueurs.ordinal()].getNom();
    }
    
    public long getTemps(IDJoueurs id) {
        return horloge.getRemainingTime(id);        
    }

    
    private void demarrerMiseAjourTemps() {
        miseAJourTemps = new Thread(new Runnable() {

            @Override
            public void run() {
                while (partieEnCours) {
                    try {
                        Thread.sleep(100);
                        aviserObservateurs(EvenementPartie.CHANGEMENT_HORLOGE, null);
                        if (horloge.getRemainingTime(IDJoueurs.JOUEUR_ARTIFICIEL) <= 0 ||
                            horloge.getRemainingTime(IDJoueurs.JOUEUR_HUMAIN) <= 0) {
                            terminerPartie();
                        }       
                    } catch (InterruptedException ex) {
                        Logger.getLogger(GestionPartie.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        });
        miseAJourTemps.start();
    }
    
    // Méthodes de l'inteface Observable

    @Override
    public void ajouterObservateur(Observateur ob) {
        observateurs.add(ob);
    }

    @Override
    public void aviserObservateurs() {
        for (Observateur ob: observateurs) {
            ob.changementEtat();
        }
    }

    @Override
    public synchronized void aviserObservateurs(Enum<?> prop, Object o) {
        for (Observateur ob: observateurs) {
            ob.changementEtat(prop, o);
        }
    }

    @Override
    public void retirerObservateur(Observateur ob) {
        observateurs.remove(ob);
    }
}
